// ts data type
"use strict"

// 1. any >> 可以是任意类型，⼀旦将变量类型限制为any，那就意味着放弃了对该变量的类型检查。
console.log("# 1. any")
// 显示声明
let a: any;

a = 'hello';
a = 100;
a = false;

// 隐式声明，没有声明类型且没有赋值的变量，会被推测成 any 。
let b;

b = 'hello';
b = 100;
b = false;

// 坑：any类型的变量，可以赋值给任意类型的变量，且不会产生警告，这会破坏掉原来的类型检查。
let c: any;
c = 9;
let s: string;
s = c; // 破坏了原有的类型，没有警告，但 s = 9 会有警告。

console.log(" ----------------------------------------------------------------------------------------------------------- ");
// 2. unknown >> 代表未知类型，可以理解成类型安全的 any ，使用于不确定的数据类型。
console.log("# 2. unknown")
let aa: unknown;
// 可以赋任意类型的值
aa = 'hello';
aa = 100;
aa = false;
// 但当赋值给其他变量时会检测其类型，相比 any ，类型安全
let ss: string;
// ss = aa; // 会有警告

// unknown 会强制开发者在使⽤之前进⾏类型检查，从而提供更强的类型安全性。
// 第一种方式，类型判断
if (typeof aa === 'string') {
    ss = aa;
    console.log(ss);
}
// 第二种方式，加断言
ss = aa as string;
console.log('log aa as string ? ', ss);
ss = <string>aa;
console.log('log ss = <string>aa ? ', ss);
console.log('typeof ss ? ', typeof ss);

// 读取 any 类型数据的任何属性都不会有警告， unknown 整好相反。
let str1: string = 'hello';
str1.toUpperCase();

let str2: any = 'hello';
str2.toUpperCase();

let str3: unknown = 'hello';
// str3.toUpperCase(); // 有警告
if (typeof str3 === 'string') {
    str3.toUpperCase();
}
(str3 as string).toUpperCase(); // 使用断言
console.log(" ----------------------------------------------------------------------------------------------------------- ");

// 3. never >> 任何值都不是，即：不能有值，例如 undefined 、 null 、 '' 、 0 都不行。
// 几乎不用never去直接限制变量，因为没有意义。 never 一般是 typescript 主动推断出来的。
console.log("# 3. never")
let aaa: string = 'hello';
if (typeof aaa === 'string') {
    console.log(aaa.toUpperCase());
} else {
    console.log(aaa); // typescript 会推断出此处的 aaa 是 never， 鼠标放在 aaa 上，会提示 never，因为没有任何一个值符合此处逻辑。
}
// never 也可以用于限制函数的返回值，返回 never 的函数不能具有可访问的终结点，及不可能正常返回。
function throwError(str: string): never {
    // return; // 返回 undefined，报错
    // return null; // 返回 null，报错
    throw new Error('error!' + str);
}
console.log(" ----------------------------------------------------------------------------------------------------------- ");
// 4. void 通常用于函数返回值，函数不返回任何值，调用者也不依赖其返回值进行任何操作。
// 函数没有显示返回值时，会隐式返回一个 undefined 。 undefined 是 void 可以接收的一种“空”。请参考以下实例：
// 无警告，隐式返回 undefined ，所以 undefined 是 void 可以接收的一种“空”。
console.log("# 4. void")
function logMsgA(msg: string): void {
    console.log(msg);
}
// 无警告，也会返回 undefined ，所以 undefined 是 void 可以接收的一种“空”。
function logMsgB(msg: string): void {
    console.log(msg);
    return;
}
// 无警告，显示返回 undefined ，所以 undefined 是 void 可以接收的一种“空”。
function logMsgC(msg: string): void {
    console.log(msg);
    return undefined;
}
// 限制函数返回值时 void 和 undefined 的区别，调用者不能依赖其返回值进行任何操作。
function logMessageA(msg: string): void{
    console.log(msg);
}
// 可以接收
let retA = logMessageA(' void 可以接收返回值。');
// if (retA) { // 此处报错， void 的值不可以使用。
//     console.log('可以使用返回值。');
// }

function logMessageB(msg: string): undefined {
    console.log(msg);
}
// 可以接收
let retB = logMessageB(' undefined 可以接收返回值。');
if (!retB) { // 此处不会报错
    console.log(' undefined 可以使用返回值。');
}
console.log(" ----------------------------------------------------------------------------------------------------------- ");
// 5. 对象类型 >> object 与 Object 在实际开发中使用相对较少，因为范围太大了。实际开发中使用频率较低。
console.log("# 5. 对象类型")
// A. object >> 表示所有非原始类型，可存储对象、函数、数组等。
let smallObject : object;
smallObject = {};
smallObject = { name : "老大" };
smallObject = [1, 3, 5, 7, 9];
smallObject = function() {};
smallObject = new String("hello");

class Person {}
smallObject = new Person();

// 以下会报警告，因为是原始类型
// smallObject = 1;
// smallObject = true;
// smallObject = 'hello';
// smallObject = null;
// smallObject = undefined;

// B. Object >> 除了undefined和null以外的任何值，只要沿着原原型链能找到Object上的方法即可，包括几个基本类型。
let bigObject: Object;

bigObject = {};
bigObject = { name : "老大" };
bigObject = [1, 3, 5, 7, 9];
bigObject = function() {};
bigObject = new String("hello");
// 本身不是 Object 的实例对象，但其包装对象是 Object 的实例，所以无警告。
bigObject = 1;
bigObject = true;
bigObject = 'hello';
// 以下会报警告
// bigObject = null;
// bigObject = undefined;

// C. 声明对象类型
// 限制一般对象
let person1: { name: string, age?: number };
let person2: { name: string; age?: number };
let person3: {
    name: string,
    age?: number
}
person1 = { name: '李四', age: 18 }
person2 = { name: '张三' }
person3 = { name: '王五' }
// 如下赋值不合法，因为person3的类型限制中，没有对gender属性的说明
// person3 = { name: '王五', gender: '男' }

// 索引签名：允许定义对象可以具有任意数量的属性，这些属性的键值类型可变，常⽤于描述类型不确定的属性（具有动态属性的对象）
// 限制person对象必须有name属性，可选age属性但值必须是数字，同时可以有任意数量、任意类型的其他属性
let person: {
    name: string
    age?: number
    [key: string]: any // 索引签名，完全可以不⽤key这个单词，换成其他的也可以，只需要保证key为字符串，值为任何值
}
// 赋值合法
person = {
    name: '张三',
    age: 18,
    gender: '男',
    city: '深圳'
}


// 声明函数类型
// 限制count有两个数字类型的参数，返回值也是数字类型的，这里=>是分隔符
let count: (a: number, b: number) => number;
// 这里定义函数时就可以省略书写参数和返回值类型检查了
count = function (x, y) {
    return x + y;
}


// 声明数组类型
let arr1: string[];
let arr2: number[];
let arr3: Array<string>; // 泛型

arr1 = ['a', 'b', 'c'];
arr2 = [1, 2, 3];
arr3 = ['hello', 'world'];
// 因为限制了类型或泛型，以下会报错
// arr1.push(1)
// arr2.push('c')
// arr3.push(1)
console.log(" ----------------------------------------------------------------------------------------------------------- ");

// 6. tuple
/**
 * 元组Tuple是⼀种特殊的数组类型，可以存储固定数量的元素，并且每个元素的类型是已知的且可以不同。
 * 元组用于精确描述⼀组值的类型，?表示可选，只有最后一个元素可以设置?。
 */
console.log("# 6. tuple")
let t1: [string, number];
let t2: [number, boolean?];
let t3: [number, string[]];
let t4: [number, ...string[]];

t1 = ['老大', 2];
t2 = [3, false];
t2 = [3];
t3 = [3, ['ss', 'sss']];
t4 = [3, 'ss', 'sss', 'ssss'];
console.log(" ----------------------------------------------------------------------------------------------------------- ");

// 7. 枚举
console.log("# 7. 枚举")

// A. 数字枚举
enum ENUM_DIRECTION {
    UP,
    DOWN,
    LEFT,
    RIGHT
}
console.log("ENUM_DIRECTION ? ", ENUM_DIRECTION)
console.log("ENUM_DIRECTION.UP ? ", ENUM_DIRECTION.UP) // 0
console.log("ENUM_DIRECTION.DOWN ? ", ENUM_DIRECTION.DOWN) // 1
console.log("ENUM_DIRECTION.LEFT ? ", ENUM_DIRECTION.LEFT) // 2
console.log("ENUM_DIRECTION.RIGHT ? ", ENUM_DIRECTION.RIGHT) // 3
console.log("ENUM_DIRECTION[0] ? ", ENUM_DIRECTION[0]) // UP
// 设置枚举起始值，之后的值会依次加一。
enum ENUM_DIRECTION_EX {
    UP = 6,
    DOWN,
    LEFT,
    RIGHT
}
console.log("ENUM_DIRECTION_EX ? ", ENUM_DIRECTION_EX)
console.log("ENUM_DIRECTION_EX.UP ? ", ENUM_DIRECTION_EX.UP) // 0
console.log("ENUM_DIRECTION_EX[0] ? ", ENUM_DIRECTION_EX[0]) // undefined
console.log("ENUM_DIRECTION_EX[6] ? ", ENUM_DIRECTION_EX[6]) // UP

// B. 字符串枚举
enum ENUM_DIRECTION_STR {
  UP = 'up',
  DOWN = 'down',
  LEFT = 'left',
  RIGHT = 'right',
}
console.log("ENUM_DIRECTION_STR.DOWN ? ", ENUM_DIRECTION_STR.DOWN) // down

// C. 常量枚举
/**
 * 量枚举是一种特殊枚举类型，它使⽤const关键字定义，在编译时会被内联，避免生成一些额外的代码。
 * 所谓“内联”其实就是TypeScript在编译时，会将枚举成员引用替换为它们的实际值，而不是生成额外
 * 的枚举对象。这可以减少生成的JavaScript代码量，并提高运行时效性能。
 */
const enum ENUM_DIRECTION_STR_CONST {
    UP = 'up',
    DOWN = 'down',
    LEFT = 'left',
    RIGHT = 'right',
}
console.log("ENUM_DIRECTION_STR_CONST.DOWN ? ", ENUM_DIRECTION_STR_CONST.DOWN) // down

console.log(" ----------------------------------------------------------------------------------------------------------- ");

// 8. type 关键字
console.log("# 8. type 关键字")
// type 可以为任意类型创建别名，让代码更简洁、可读性更强，同时能更方便地进行类型复用和扩展。
type num = number
let price: num
price = 150
console.log("price: num ?",price)
// A. 联合类型
// 联合类型是一种高级类型，它表示一个值可以是几种不同类型之一。通过|定义。
type Status= number | string // 只能是 number 类型或者 string 类型
type Gender = "male" | "female" // 只能是 "male" 或者 "female" 

let strStatus: Status = "status"
let numStatus: Status = 1
// let blnStatus: Status = true // 出现警告
let g: Gender = "male"
// let gg: Gender = "男" // 出现警告

// B. 交叉类型
// 可以将多个类型合并为一个类型，合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。通过 & 合并。

// 面积
type Position = {
    longitude: number // 经度
    latitude: number // 纬度
}

// 地址
type Addr = {
    num: number // 楼号
    cell: number // 单元号
    room: string // 房间号
}
// 合并面积和地址为交叉类型
type Room = Position & Addr
const r: Room = {
    longitude: 31,
    latitude: 106,
    num: 6,
    cell: 3,
    room: "803"
}

// C. 一个特殊情况，这里可以返回，最好不要这样使用，很迷惑。
type LogFunc = () => void // 此类型的函数不接收参数，返回值为 void 。
const f1: LogFunc = () => {
    return 100 // 允许非空返回
}
const f2: LogFunc = () => 200 // 允许非空返回
const f3: LogFunc = function() {
    return 300 // 允许非空返回
}
let f1Value = f1()
let f2Value = f2()
let f3Value = f3()
console.log("f1Value ? ", f1Value)
console.log("f2Value ? ", f2Value)
console.log("f3Value ? ", f3Value)

const src = [1, 2, 3]
const dist = [0]
src.forEach((item, index) => {
    console.log(index + " --- " + item)
    dist.push(item)
})
console.log("dist ? ", dist)